package cc.eddic.pratice.programmingparadigms.nginxlog.impl;

import cc.eddic.pratice.programmingparadigms.nginxlog.LogItem;
import cc.eddic.pratice.programmingparadigms.nginxlog.LogItemParser;
import cc.eddic.pratice.programmingparadigms.nginxlog.NginxLogItem;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import lombok.val;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;

@Slf4j
public class LogItemParserGcsImpl implements LogItemParser {
    @Override
    public Optional<LogItem> parse(NginxLogItem nginxLogItem) {
        LocalDateTime ldt;
        try {
            ldt = LocalDateTime.parse(nginxLogItem.getTime(), DATE_TIME_FORMATTER);
        } catch (DateTimeParseException ex) {
            log.error("Error parsing time: {}", nginxLogItem.getTime(), ex);
            return Optional.empty();
        }

        val requestMatcher = PATTERN.matcher(nginxLogItem.getRequest());
        if (!requestMatcher.matches()) {
            log.error("Error parsing request: {}", nginxLogItem.getRequest());
            return Optional.empty();
        }

        int status;
        try {
            status = Integer.parseInt(nginxLogItem.getStatus());
        } catch (NumberFormatException ex) {
            log.error("Error parsing status: {}", nginxLogItem.getStatus(), ex);
            return Optional.empty();
        }

        long bytes;
        try {
            bytes = Long.parseLong(nginxLogItem.getBytes());
        } catch (NumberFormatException ex) {
            log.error("Error parsing bytes: {}", nginxLogItem.getBytes(), ex);
            return Optional.empty();
        }

        String path;
        Map<String, String> query;
        val o = parse(requestMatcher.group("url"));
        if (o.isPresent()) {
            path = o.get().getUrl();
            query = o.get().getQuery();
        } else {
            path = requestMatcher.group("url");
            query = new HashMap<>();
        }

        return Optional.of(new LogItem(nginxLogItem.getIp(), ldt, requestMatcher.group("method"), path, query, requestMatcher.group("protocol"), status, bytes, nginxLogItem.getReferer(), nginxLogItem.getUserAgent()));
    }

    private String decode(String url) {
        try {
            return URLDecoder.decode(url, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            log.error(e.getMessage(), e);
            return url;
        }
    }

    private Optional<UrlModel> parse(String url) {
        val x = url.split("\\?");
        if (x.length < 1) {
            log.error("Error parsing url: {}", url);
            return Optional.empty();
        }

        Map<String, String> query = new HashMap<>();
        if (x.length > 1) {
            val q = x[1].split("&");
            for (String s : q) {
                val kv = s.split("=");
                if (kv.length == 2) {
                    query.put(kv[0], decode(kv[1]));
                }
            }
            if (log.isDebugEnabled()) log.debug("query {} of {}", query.size(), q);
        }
        return Optional.of(new UrlModel(x[0], query));

    }

    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("dd/MMM/yyyy:HH:mm:ss Z");

    private static final Pattern PATTERN = Pattern.compile("(?<method>\\w+)\\s+(?<url>[^ ]+)\\s+(?<protocol>[^ ]+)");

    @Getter
    @AllArgsConstructor
    private static class UrlModel {
        private String url;
        private Map<String, String> query;
    }
}
